#include "repoutil.h"
#include "configutil.h"

#include <QDir>
#include <QFileInfo>
#include <QJsonDocument>
#include <QProcess>
#include <qd.h>
#include <response.h>

RepoUtil::RepoUtil() {}

bool RepoUtil::isVerify(QString &dir)
{

    qd << dir;
    if (dir.isEmpty() || QFileInfo(dir).isRoot()) return false;

    QFileInfo fi(dir);
    if (fi.exists() && fi.isDir()) {
        QFileInfoList localEntryInfoList = QDir(dir).entryInfoList(QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot | QDir::Filter::Hidden);
        foreach (auto entry, localEntryInfoList) {
            if (entry.fileName().compare(".repo") == 0) {
                return true;
            }
        }
    }

    dir = fi.path();

    // 查找上级
    return dir != "." && isVerify(dir);
}

QString RepoUtil::_paths(const QStringList &paths)
{
    return paths.join(QDir::separator());
}

QString RepoUtil::_paths(const QString &path, const QString &path1)
{
    QStringList paths;
    paths << path;
    paths << path1;
    return _paths(paths);
}

QString RepoUtil::_paths(const QString &path, const QString &path1, const QString &path2)
{
    QStringList paths;
    paths << path;
    paths << path1;
    paths << path2;
    return _paths(paths);
}

QString RepoUtil::_paths(const QString &path, const QString &path1, const QString &path2, const QString &path3)
{
    QStringList paths;
    paths << path;
    paths << path1;
    paths << path2;
    paths << path3;
    return _paths(paths);
}

QString RepoUtil::_paths(const QString &path, const QString &path1, const QString &path2, const QString &path3, const QString &path4)
{
    QStringList paths;
    paths << path;
    paths << path1;
    paths << path2;
    paths << path3;
    paths << path4;
    return _paths(paths);
}

bool RepoUtil::_dir_exists(const QString &dir)
{
    return QFileInfo::exists(dir);
}

bool RepoUtil::_dir_exists(const QString &dir, const QString &dir1)
{
    return _dir_exists(_paths(dir, dir1));
}

bool RepoUtil::_dir_exists(const QString &dir, const QString &dir1, const QString &dir2)
{
    return _dir_exists(_paths(dir, dir1, dir2));
}

bool RepoUtil::_dir_exists(const QString &dir, const QString &dir1, const QString &dir2, const QString &dir3)
{
    return _dir_exists(_paths(dir, dir1, dir2, dir3));
}

QString RepoUtil::manifest(const QString &dir)
{
    return run(dir, ConfigUtil::getAppRepoConfig(), QStringList("manifest"));
}

QString RepoUtil::manifest_json_str(const QString &dir)
{
    return run(dir, ConfigUtil::getAppRepoConfig(), QStringList("manifest") << "--json");
}

QJsonDocument RepoUtil::manifest_json_document(const QString &dir)
{
    return QJsonDocument::fromJson(manifest_json_str(dir).toUtf8());
}

QJsonObject RepoUtil::manifest_json_object(const QString &dir)
{
    QJsonDocument doc = QJsonDocument::fromJson(manifest_json_str(dir).toUtf8());
    return doc.object();
}

Result RepoUtil::manifest_branch(const QString &dir)
{
    Result v;
    auto cmd = "git";
    auto arguments = QStringList("branch") << "--show-current";
    auto manifestdir = _paths(dir, ".repo/manifests");
    if (!QDir(manifestdir).exists()) {
        v.a = false;
        v.b = "目录不存在";
        goto out;
    }

    v.a = run_exit_code(manifestdir, cmd, arguments) == 0;
    v.b = run(manifestdir, cmd, arguments).trimmed();

out:
    return v;
}

QString RepoUtil::info(const QString &dir, const QString &name)
{
    return run(dir, ConfigUtil::getAppRepoConfig(), QStringList("info") << name, true);
}

QString RepoUtil::revision(const QString &dir)
{
    return default_revision(dir);
}

QString RepoUtil::default_remote(const QString &dir)
{
    return Response(0, manifest_json_str(dir)).toMap().value("default").toMap().value("remote").toString();
}

QString RepoUtil::default_revision(const QString &dir)
{
    return Response(0, manifest_json_str(dir)).toMap().value("default").toMap().value("revision").toString();
}

QString RepoUtil::default_sync_j(const QString &dir)
{
    return Response(0, manifest_json_str(dir)).toMap().value("default").toMap().value("sync-j").toString();
}

QVariantList RepoUtil::project(const QString &dir)
{
    return Response(0, manifest_json_str(dir)).toMap().value("project").toList();
}

QStringList RepoUtil::project_names(const QString &dir)
{
    QStringList names;
    foreach (auto var, project(dir)) {
        auto name = var.toMap().value("name").toString();
        auto path = var.toMap().value("path").toString();
        names << name;
    }
    return names;
}

QVariantList RepoUtil::remote(const QString &dir)
{
    return Response(0, manifest_json_str(dir)).toMap().value("remote").toList();
}

/**
 * @brief RepoUtil::repo_project_name_validated
 * @param dir   工作目录
 * @param name  项目名称
 * @return exists(<dir>/.repo/project-objects/<name>.git)
 */
bool RepoUtil::repo_project_objects_name_exists(const QString &dir, const QString &name)
{
    auto project_objects_name = _paths(dir, ".repo/project-objects/", name);
    qd << project_objects_name << project_objects_name + ".git";
    return QDir(project_objects_name).exists() || QDir(project_objects_name + ".git").exists();
}

/**
 * @brief RepoUtil::repo_project_name_validated
 * @param dir   工作目录
 * @param name  项目名称
 * @return exists(<dir>/.repo/project-objects/<name>.git/config)
 */
bool RepoUtil::repo_project_objects_name_config_validated(const QString &dir, const QString &name)
{
    auto project_objects_name = _paths(dir, ".repo/project-objects", name);
    auto project_objects_name_git = _paths(dir, ".repo/project-objects/",name + ".git");
    auto config = _paths(project_objects_name, "config");
    auto config_git = _paths(project_objects_name_git, "config");
    qd << config << config_git;
    return QFileInfo(config).exists() || QFileInfo(config_git).exists();
}

/**
 * @brief RepoUtil::repo_projects_path_exists
 * @param dir   工作目录
 * @param path  项目路径
 * @return exists(<dir>/.repo/projects/<path>.git)
 */
bool RepoUtil::repo_projects_path_exists(const QString &dir, const QString &path)
{
    auto projects_path = _paths(dir, ".repo/projects/", path);
    auto projects_path_git = _paths(dir, ".repo/projects/", path + ".git");
    qd << projects_path << projects_path_git;
    return QDir(projects_path).exists() || QDir(projects_path_git).exists();
}
/**
 * @brief RepoUtil::repo_projects_path_3rlinks_validated
 * @param dir   工作目录
 * @param name  项目名称
 * @param path  项目路径
 * @return readlink("hooks,objects,rr-cache")
 */
bool RepoUtil::repo_projects_path_3rlinks_validated(const QString &dir, const QString &name, const QString &path)
{
    if (repo_project_objects_name_exists(dir, name) == false) {
        return false;
    }

    if (repo_projects_path_exists(dir, path) == false) {
        return false;
    }

    auto projects_path = _paths(dir, ".repo/projects/", path);
    auto projects_path_git = _paths(dir, ".repo/projects/", path + ".git");

    QStringList dirs;
    dirs << "hooks" << "objects" << "rr-cache";

    foreach (auto var, dirs) {
        QFileInfo info(_paths(projects_path, var));
        QFileInfo info_git(_paths(projects_path_git, var));

        qd << info.filePath() << QFileInfo::exists(info.filePath());
        qd << info.isSymLink();
        qd << info.symLinkTarget() << QFile::exists(info.symLinkTarget());

        qd << info_git.filePath() << QFileInfo::exists(info_git.filePath());
        qd << info_git.isSymLink();
        qd << info_git.symLinkTarget() << QFile::exists(info_git.symLinkTarget());

        if (info.exists() || info_git.exists()) {
            if (info_git.isSymLink() || info_git.isSymLink()) {
                if (QFile::exists(info.symLinkTarget()) || QFile::exists(info_git.symLinkTarget())) {
                    continue;
                }
            }
        }

        return false;
    }

    return true;
}

Validate RepoUtil::repo_info_validate(const QString &dir, const QString &name)
{
    auto output = info(dir, name);

    Validate iv = {true, ""};
    foreach (auto var, output.split("\n", Qt::SkipEmptyParts)) {
        if (var.startsWith("error") && var.contains("revision") && var.contains("not found")) {
            iv.a = false;
            iv.b = QString("info(revision not found)");
            break;
        }
    }

    return iv;
}

/**
 * @brief RepoUtil::repo_info_validate_slove_revision_not_found_fetch_missing_revision
 * @param dir   工作目录
 * @param name  项目名称
 * @param path  项目路径
 * @return
 */
QString RepoUtil::repo_info_validate_slove_revision_not_found_fetch_missing_revision(const QString &dir, const QString &name, const QString &path)
{
    return QString();
}

void RepoUtil::repo_info_validate_slove_revision_not_found_delete_and_resynchronize(const QString &dir, const QString &name, const QString &path)
{
    auto project_objects_name_git = _paths(dir, ".repo/project-objects/", name + ".git");
    auto projects_path_git = _paths(dir, ".repo/projects/", path + ".git");
    auto realpath = _paths(dir, path);

    QDir target;
    target = QDir(project_objects_name_git);
    qd << "执行删除操作:" << target.removeRecursively() << project_objects_name_git;
    target = QDir(projects_path_git);
    qd << "执行删除操作:" << target.removeRecursively() << projects_path_git;
    target = QDir(realpath);
    qd << "执行删除操作:" << target.removeRecursively() << realpath;
}

/**
 * @brief 检查目录是否可瘦身
 * @param dir   工作目录
 * @param name  项目名称
 * @return 是否存在 tmp_pack_ 名称开头的文件
 */
bool RepoUtil::repo_project_objects_name_objects_pack_exists_tmp_pack_file(const QString &dir, const QString &name)
{
    auto project_objects_name = _paths(dir, ".repo/project-objects/", name);
    auto project_objects_name_git = _paths(dir, ".repo/project-objects/", name + ".git");

    auto realpath_objects_pack = _paths(project_objects_name_git, "objects/pack");

    foreach (auto info, QDir(realpath_objects_pack).entryInfoList()) {
        if (info.fileName().startsWith("tmp_pack_")) {
            return true;
        }
    }

    return false;
}

/**
 * @brief 对 .repo/project-objects/<name>.git/objects/pack 目录进行瘦身
 * @param dir   工作目录
 * @param name  项目名称
 */
void RepoUtil::repo_project_objects_name_objects_pack_clean_tmp_pack_file(const QString &dir, const QString &name)
{
    auto project_objects_name_git = _paths(dir, ".repo/project-objects/", name + ".git");
    auto realpath_objects_pack = _paths(project_objects_name_git, "objects/pack");
    foreach (auto info, QDir(realpath_objects_pack).entryInfoList()) {
        if (info.fileName().startsWith("tmp_pack_")) {
            QFile::remove(info.filePath());
        }
    }
}

QString RepoUtil::query_path_is_checked(const QString &dir, const QString &path)
{
    QString value = "未检出";
    if (QFileInfo::exists(_paths(dir, path))) {
        value = "已检出";
    }
    return value;
}

Validate RepoUtil::query_path_git_workspace_is_ok(const QString &dir, const QString &path)
{
    // auto realpath = _paths(dir, path);
    // return run_exit_code(realpath, "git", QStringList("status")) == 0;
    Validate v {true, ""};

    // 检查点
    // 1. 检查是否未检出
    if (!_dir_exists(dir,path)) {
        v.a = false;
        v.b = "path not exists";
    }

    // 2. 检查是否缺失 .git 目录
    if (!_dir_exists(dir, path,".git")) {
        v.a = false;
        v.b = ".git not exists";
    }

    if (path.contains("mako-kernel")) {
        qd;
    }

    // 3. 检查是否为空目录
    auto entries = QDir(_paths(dir, path)).entryInfoList(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot);
    if (entries.count() <= 1) {
        v.a = false;
        v.b = "空项目";
    }
    return v;
}

void RepoUtil::git_workspace_init(const QString &dir, const QString &path)
{
    auto realpath = _paths(dir, path);
    run(realpath, "git", QStringList("init"));
}

QString RepoUtil::run(const QString &dir, const QString &cmd, const QStringList &args, bool merge_channel)
{
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
    env.insert("REPO_TRACE", "0");

    QProcess process;
    process.setProcessEnvironment(env);
    process.setWorkingDirectory(dir);
    if (merge_channel) process.setProcessChannelMode(QProcess::MergedChannels);
    process.start(cmd, args);
    process.waitForFinished();
    QByteArray content = process.readAll();
    return content;
}

int RepoUtil::run_exit_code(const QString &dir, const QString &cmd, const QStringList &args)
{
    QProcess process;
    process.setWorkingDirectory(dir);
    process.start(cmd, args);
    process.waitForFinished();
    return process.exitCode();
}
